package cn.tannn.captcha.api.impl;

import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.CircleCaptcha;
import cn.hutool.captcha.LineCaptcha;
import cn.hutool.captcha.ShearCaptcha;
import cn.hutool.captcha.generator.MathGenerator;
import cn.hutool.captcha.generator.RandomGenerator;
import cn.tannn.captcha.api.CaptchaCacheService;
import cn.tannn.captcha.api.CaptchaService;
import cn.tannn.captcha.api.config.CaptchaMapDefConfig;
import cn.tannn.captcha.api.config.ConfigKey;
import cn.tannn.captcha.sdk.dict.CaptchaType;
import cn.tannn.captcha.sdk.exception.CaptChaException;
import cn.tannn.captcha.sdk.exception.CaptChaExceptionMsg;
import cn.tannn.captcha.sdk.generator.EnhancedMathGenerator;
import cn.tannn.captcha.sdk.module.CaptchaVO;
import cn.tannn.captcha.sdk.module.SlideCaptcha;
import cn.tannn.captcha.sdk.service.factory.VerifyCaptchaFactory;
import cn.tannn.captcha.sdk.slide.SlideCaptchaService;
import cn.tannn.captcha.sdk.util.CaptchaFontUtil;
import jakarta.servlet.http.HttpServletRequest;

import java.util.Map;
import java.util.Objects;

/**
 * 验证码服务
 *
 * @author <a href="https://t.tannn.cn/">tan</a>
 * @version V1.0
 * @date 2024/12/9 11:44
 */
public class CaptchaServiceImpl implements CaptchaService {

    private final CaptchaCacheService captchaCacheService;
    /**
     * 滑动验证码
     */
    private final SlideCaptchaService slideCaptchaService;

    private CaptchaFontUtil captchaFontUtil;

    public CaptchaServiceImpl(CaptchaCacheService captchaCacheService
            , SlideCaptchaService slideCaptchaService) {
        this.captchaCacheService = captchaCacheService;
        this.slideCaptchaService = slideCaptchaService;
        // 全局设置验证码字体配置
        this.captchaFontUtil = new CaptchaFontUtil(captchaCacheService.captchaSetting());

    }

    @Override
    public CaptchaVO math(HttpServletRequest request) {
        return math(request, CaptchaMapDefConfig.DEFAULT_MATH_CONFIG);
    }

    @Override
    public CaptchaVO math(HttpServletRequest request, Map<String, Integer> config) {
        CircleCaptcha captcha = captchaFontUtil.createOptimizedCircleCaptcha(
                config.getOrDefault(ConfigKey.WIDTH, 160),
                config.getOrDefault(ConfigKey.HEIGHT, 45),
                config.getOrDefault(ConfigKey.CODE_COUNT, 5),
                config.getOrDefault(ConfigKey.CIRCLE_COUNT, 2)
        );
        // 自定义验证码内容为四则运算方式
        captcha.setGenerator(new EnhancedMathGenerator(1,false));
        // 重新生成code
        captcha.createCode();
        CaptchaVO build = new CaptchaVO(
                captcha.getImageBase64Data()
                , captcha.getCode()
                , captchaCacheService.captchaSetting().getExpireTime()
                , CaptchaType.MATH
        );
        // 存储，验证用
        captchaCacheService.storageImageCaptcha(build, request);
        return build;
    }

    @Override
    public CaptchaVO absMath(HttpServletRequest request) {
        return absMath(request, CaptchaMapDefConfig.DEFAULT_ABS_MATH_CONFIG);
    }

    @Override
    public CaptchaVO absMath(HttpServletRequest request, Map<String, Integer> config) {
        CircleCaptcha captcha = captchaFontUtil.createOptimizedCircleCaptcha(
                config.getOrDefault(ConfigKey.WIDTH, 160),
                config.getOrDefault(ConfigKey.HEIGHT, 45),
                config.getOrDefault(ConfigKey.CODE_COUNT, 5),
                config.getOrDefault(ConfigKey.CIRCLE_COUNT, 2)
        );
        // 使用绝对值数学计算生成器
        captcha.setGenerator(new EnhancedMathGenerator(config.getOrDefault(ConfigKey.NUMBER_LENGTH, 1),true));
        captcha.createCode();
        CaptchaVO build = new CaptchaVO(
                captcha.getImageBase64Data(),
                captcha.getCode(),
                captchaCacheService.captchaSetting().getExpireTime(),
                CaptchaType.MATH
        );
        captchaCacheService.storageImageCaptcha(build, request);
        return build;
    }

    @Override
    public CaptchaVO number(HttpServletRequest request) {
        return number(request, CaptchaMapDefConfig.DEFAULT_NUMBER_CONFIG);
    }

    @Override
    public CaptchaVO number(HttpServletRequest request, Map<String, Integer> config) {
        LineCaptcha captcha = captchaFontUtil.createOptimizedLineCaptcha(
                config.getOrDefault(ConfigKey.WIDTH, 160),
                config.getOrDefault(ConfigKey.HEIGHT, 45),
                config.getOrDefault(ConfigKey.CODE_COUNT, 4),
                config.getOrDefault(ConfigKey.LINE_COUNT, 2)
        );
        captcha.setGenerator(new RandomGenerator("0123456789", config.getOrDefault(ConfigKey.CODE_COUNT, 4)));
        captcha.createCode();
        CaptchaVO build = new CaptchaVO(
                captcha.getImageBase64Data(),
                captcha.getCode(),
                captchaCacheService.captchaSetting().getExpireTime(),
                CaptchaType.NUMBER
        );
        captchaCacheService.storageImageCaptcha(build, request);
        return build;
    }

    @Override
    public CaptchaVO line(HttpServletRequest request) {
        return line(request, CaptchaMapDefConfig.DEFAULT_LINE_CONFIG);
    }

    @Override
    public CaptchaVO line(HttpServletRequest request, Map<String, Integer> config) {
        LineCaptcha captcha = captchaFontUtil.createOptimizedLineCaptcha(
                config.getOrDefault(ConfigKey.WIDTH, 160),
                config.getOrDefault(ConfigKey.HEIGHT, 45),
                config.getOrDefault(ConfigKey.CODE_COUNT, 4),
                config.getOrDefault(ConfigKey.LINE_COUNT, 2)
        );
        CaptchaVO build = new CaptchaVO(
                captcha.getImageBase64Data(),
                captcha.getCode(),
                captchaCacheService.captchaSetting().getExpireTime(),
                CaptchaType.LINE
        );
        captchaCacheService.storageImageCaptcha(build, request);
        return build;
    }

    @Override
    public CaptchaVO circle(HttpServletRequest request) {
        return circle(request, CaptchaMapDefConfig.DEFAULT_CIRCLE_CONFIG);
    }

    @Override
    public CaptchaVO circle(HttpServletRequest request, Map<String, Integer> config) {
        CircleCaptcha captcha = captchaFontUtil.createOptimizedCircleCaptcha(
                config.getOrDefault(ConfigKey.WIDTH, 160),
                config.getOrDefault(ConfigKey.HEIGHT, 45),
                config.getOrDefault(ConfigKey.CODE_COUNT, 4),
                config.getOrDefault(ConfigKey.CIRCLE_COUNT, 2)
        );
        CaptchaVO build = new CaptchaVO(
                captcha.getImageBase64Data(),
                captcha.getCode(),
                captchaCacheService.captchaSetting().getExpireTime(),
                CaptchaType.CIRCLE
        );
        captchaCacheService.storageImageCaptcha(build, request);
        return build;
    }

    @Override
    public CaptchaVO shear(HttpServletRequest request) {
        return shear(request, CaptchaMapDefConfig.DEFAULT_SHEAR_CONFIG);
    }

    @Override
    public CaptchaVO shear(HttpServletRequest request, Map<String, Integer> config) {
        ShearCaptcha captcha = captchaFontUtil.createCharShearCaptcha(
                config.getOrDefault(ConfigKey.WIDTH, 160),
                config.getOrDefault(ConfigKey.HEIGHT, 45),
                config.getOrDefault(ConfigKey.CODE_COUNT, 4),
                config.getOrDefault(ConfigKey.THICKNESS, 4)
        );
        CaptchaVO build = new CaptchaVO(
                captcha.getImageBase64Data(),
                captcha.getCode(),
                captchaCacheService.captchaSetting().getExpireTime(),
                CaptchaType.SHEAR
        );
        captchaCacheService.storageImageCaptcha(build, request);
        return build;
    }

    @Override
    public CaptchaVO slide(HttpServletRequest request) {
        SlideCaptcha captcha = slideCaptchaService.getCaptcha(request);
        // 缓存
        CaptchaVO build = new CaptchaVO();
        build.setOvertime(captchaCacheService.captchaSetting().getExpireTime());
        // getBlockX -  answer(SlideCaptcha.value) < 允许偏差
        build.setCaptcha(String.valueOf(Objects.isNull(captcha.getBlockX()) ? "0" : captcha.getBlockX()));
        build.setCaptchaType(CaptchaType.SLIDE);

        // 存储，验证用
        captchaCacheService.storageImageCaptcha(build, request);
        // 这个不能返回给前端
        captcha.setBlockX(null);
        // 这个没必要存
        build.setSlide(captcha);
        return build;
    }

    @Override
    public void verifyCaptchaJe(String answer, HttpServletRequest request) throws IllegalAccessException {
        CaptchaVO redisCaptcha = captchaCacheService.loadImageCaptcha(request)
                .orElseThrow(() -> new CaptChaException(CaptChaExceptionMsg.CAPTCHA));
        Boolean aBoolean = verifyCaptcha(answer, redisCaptcha);
        if (!aBoolean) {
            throw CaptChaException.specialMessage(CaptChaExceptionMsg.CAPTCHA);
        }
    }

    @Override
    public void verifyCaptcha(String answer, HttpServletRequest request) throws IllegalAccessException {
        CaptchaVO redisCaptcha = captchaCacheService.loadImageCaptcha(request)
                .orElseThrow(() -> new CaptChaException(CaptChaExceptionMsg.CAPTCHA));
        Boolean aBoolean = verifyCaptcha(answer, redisCaptcha);
        if (!aBoolean) {
            throw new CaptChaException(CaptChaExceptionMsg.CAPTCHA);
        }
    }

    @Override
    public void verifyCaptcha(String answer, String ip) throws IllegalAccessException {
        CaptchaVO redisCaptcha = captchaCacheService.loadImageCaptcha(ip)
                .orElseThrow(() -> new CaptChaException(CaptChaExceptionMsg.CAPTCHA));
        Boolean aBoolean = verifyCaptcha(answer, redisCaptcha);
        if (!aBoolean) {
            throw new CaptChaException(CaptChaExceptionMsg.CAPTCHA);
        }
    }
    @Override
    public void verifyCaptchaJe(String answer, String ip) throws IllegalAccessException {
        CaptchaVO redisCaptcha = captchaCacheService.loadImageCaptcha(ip)
                .orElseThrow(() -> new CaptChaException(CaptChaExceptionMsg.CAPTCHA));
        Boolean aBoolean = verifyCaptcha(answer, redisCaptcha);
        if (!aBoolean) {
            throw CaptChaException.specialMessage(CaptChaExceptionMsg.CAPTCHA);
        }
    }

    /**
     * 验证验证码
     *
     * @param answer       回答
     * @param redisCaptcha 问题
     * @return ture 对了
     * @throws IllegalAccessException IllegalAccessException
     */
    private static Boolean verifyCaptcha(String answer, CaptchaVO redisCaptcha) throws IllegalAccessException {
        String question = redisCaptcha.getCaptcha();
        VerifyCaptchaFactory captchaFactory = new VerifyCaptchaFactory(redisCaptcha.getCaptchaType());
        return captchaFactory.verifyCaptcha(question, answer);
    }
}
